package co.recloud.ariadne.store;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;

import co.recloud.ariadne.model.Location;
import co.recloud.ariadne.server.DataServer;
import co.recloud.ariadne.thread.Main;

public class MemoryStore {
	private static ConcurrentMap<Location, MemoryStore> storesSingleton = new ConcurrentHashMap<Location, MemoryStore>();
	
	public static MemoryStore getStore(Location location) {
		HostTable ht = HostTable.getInstance();
		if (storesSingleton == null) {
			storesSingleton = new ConcurrentHashMap<Location, MemoryStore>();
		}
		if (location != null && ht.isReplica(location)) {
			storesSingleton.putIfAbsent(location, new MemoryStore());
		}
		return storesSingleton.get(location);
	}

	private ConcurrentMap<String, ConcurrentNavigableMap<Long, ConcurrentNavigableMap<Long, ConcurrentMap<String, Object>>>> data;

	public MemoryStore() {
		data = new ConcurrentHashMap<String, ConcurrentNavigableMap<Long, ConcurrentNavigableMap<Long, ConcurrentMap<String, Object>>>>();
	}

	private String toPath(String schema, String columnFamily, String key) {
		return schema + "." + columnFamily + ":" + key;
	}

	private boolean checkPath(String path, Long time, boolean create) {
		if (checkPath(path, create)) {
			boolean hasTime = false;
			if (create) {
				data.get(path)
						.putIfAbsent(
								time,
								new ConcurrentSkipListMap<Long, ConcurrentMap<String, Object>>());
				hasTime = true;
			} else {
				hasTime = !data.get(path).headMap(time + 1).isEmpty();
			}
			return hasTime;
		}
		return false;
	}

	private void createPath(String path, Long time, Long id) {
		if (checkPath(path, time, true)) {
			data.get(path).get(time)
					.putIfAbsent(id, new ConcurrentHashMap<String, Object>());
		}
	}

	public Map<String, Object> get(String schema, String columnFamily,
			String key, Long time) {
		Map<String, Object> result = null;
		String path = toPath(schema, columnFamily, key);
		if (checkPath(path, time, false)) {
			ConcurrentNavigableMap<Long, ConcurrentNavigableMap<Long, ConcurrentMap<String, Object>>> timeMap = data
					.get(path).headMap(time + 1);
			ConcurrentNavigableMap<Long, ConcurrentMap<String, Object>> idMap = timeMap
					.get(timeMap.lastKey());
			result = idMap.get(idMap.lastKey());
		}
		return result;
	}

	public Long getTime(String schema, String columnFamily, String key,
			Long time) {
		Long result = null;
		String path = toPath(schema, columnFamily, key);
		if (checkPath(path, time, false)) {
			ConcurrentNavigableMap<Long, ConcurrentNavigableMap<Long, ConcurrentMap<String, Object>>> timeMap = data
					.get(path).headMap(time + 1);
			result = timeMap.lastKey();
		}
		return result;
	}

	public void put(String schema, String columnFamily, String key, Long time,
			Long id, Map<String, Object> row) {
		String path = toPath(schema, columnFamily, key);
		createPath(path, time, id);
		data.get(path).get(time).get(id).putAll(row);
	}

	private boolean checkPath(String path, boolean create) {
		if (create) {
			data.putIfAbsent(
					path,
					new ConcurrentSkipListMap<Long, ConcurrentNavigableMap<Long, ConcurrentMap<String, Object>>>());
		} else if (!data.containsKey(path)) {
			return false;
		}
		return true;
	}

	public Set<String> getAllPaths() {
		Set<String> paths = new HashSet<String>();
		paths.addAll(data.keySet());
		return paths;
	}

	public SortedMap<Long, SortedMap<Long, Map<String, Object>>> getAllVersions(
			String path) {
		SortedMap<Long, SortedMap<Long, Map<String, Object>>> versions = new TreeMap<Long, SortedMap<Long, Map<String, Object>>>();
		for (Long time : data.get(path).keySet()) {
			versions.put(time, new TreeMap<Long, Map<String, Object>>());
			for (Long id : data.get(path).get(time).keySet()) {
				versions.get(time).put(id, new HashMap<String, Object>());
				versions.get(time).get(id)
						.putAll(data.get(path).get(time).get(id));
			}
		}
		return versions;
	}

	public void putAllVersions(String path,
			SortedMap<Long, SortedMap<Long, Map<String, Object>>> versions) {
		checkPath(path, true);
		for (Long time : versions.keySet()) {
			for (Long id : versions.get(time).keySet()) {
				createPath(path, time, id);
				data.get(path).get(time).get(id)
						.putAll(versions.get(time).get(id));
			}
		}
	}

	public void condensePath(String path) {
		SortedSet<Long> activeTimes = DataServer.getActiveStartTimes();
		activeTimes.add(Main.getTime());
		SortedMap<Long, SortedMap<Long, Map<String, Object>>> versions = getAllVersions(path);
		Set<Long> timesToKeep = new HashSet<Long>();
		Set<Long> timesToRemove = new HashSet<Long>();
		timesToRemove.addAll(versions.keySet());
		for(Long time : activeTimes) {
			SortedMap<Long, SortedMap<Long, Map<String, Object>>> headMap = versions.headMap(time + 1);
			if(!headMap.isEmpty()) {
				Long timeToKeep = headMap.lastKey();
				timesToKeep.add(timeToKeep);
				Set<Long> idsToRemove = new HashSet<Long>();
				idsToRemove.addAll(versions.get(timeToKeep).keySet());
				idsToRemove.remove(versions.get(timeToKeep).lastKey());
				for(Long id : idsToRemove) {
					versions.get(timeToKeep).remove(id);
				}
			}
		}
		timesToRemove.removeAll(timesToKeep);
		for(Long time : timesToRemove) {
			versions.remove(time);
		}
	}
}
